﻿using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TeamFoundation;

namespace CoryCissellMikeParks.Parkware_AutoShelve
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the
    /// IVsPackage interface and uses the registration attributes defined in the framework to
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    // This attribute is used to register the information needed to show the this package
    // in the Help/About dialog of Visual Studio.
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.SolutionExists)]
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideOptionPageAttribute(typeof(OptionsPageGeneral), "TFS Auto Shelve", "General", 101, 106, true)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidParkware_AutoShelvePkgString)]
    public sealed class Parkware_AutoShelvePackage : Package, IVsSolutionEvents
    {
        private TfsAutoShelve autoShelve = null;
        private DTE2 dte2 = null;
        private IVsSolution2 solution = null;
        private uint solutionEventsCookie;
        private const string TeamFoundationServerExtService = "Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt";


        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public Parkware_AutoShelvePackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        private OleMenuCommand MenuCommandRunNow
        {
            get;
            set;
        }

        private OleMenuCommand MenuCommandRunState
        {
            get;
            set;
        }

        private string MenuCommandTextRunning
        {
            get;
            set;
        }

        private string MenuCommandTextStopped
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the options.
        /// </summary>
        /// <value>The options.</value>
        private OptionsPageGeneral Options
        {
            get;
            set;
        }

        public int OnAfterCloseSolution(object pUnkReserved)
        {
            if (autoShelve != null)
            {
                autoShelve.CreateShelveSet();
                autoShelve.Terminate();

                autoShelve.OnTfsAutoShelveTimerStart -= new TfsAutoShelve.TfsAutoShelveTimerStart(DisplayRunState);
                autoShelve.OnTfsAutoShelveTimerStop -= new TfsAutoShelve.TfsAutoShelveTimerStop(DisplayRunState);
                autoShelve.OnTfsAutoShelveWorkSpaceDiscovery -= new TfsAutoShelve.TfsAutoShelveWorkSpaceDiscovery(autoShelve_OnTfsAutoShelveWorkSpaceDiscovery);

                this.Options.OnIntervalChanged -= new OptionsPageGeneral.TimeIntervalChanged(Options_OnIntervalChanged);
            }

            return VSConstants.S_OK;
        }

        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
        {
            autoShelve.OnTfsAutoShelveTimerStart += new TfsAutoShelve.TfsAutoShelveTimerStart(DisplayRunState);
            autoShelve.OnTfsAutoShelveTimerStop += new TfsAutoShelve.TfsAutoShelveTimerStop(DisplayRunState);
            autoShelve.OnTfsAutoShelveWorkSpaceDiscovery += new TfsAutoShelve.TfsAutoShelveWorkSpaceDiscovery(autoShelve_OnTfsAutoShelveWorkSpaceDiscovery);

            autoShelve.WorkingDirectory = Directory.GetParent(dte2.Solution.FullName).FullName;

            autoShelve.TimerInterval = this.Options.TimerSaveInterval;
            autoShelve.BaseShelvesetName = this.Options.BaseName;

            this.Options.OnIntervalChanged += new OptionsPageGeneral.TimeIntervalChanged(Options_OnIntervalChanged);

            autoShelve.StartTimer();

            return VSConstants.S_OK;
        }

        public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeCloseSolution(object pUnkReserved)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initialization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            // Initialize Visual Studio Activity Log. In order to view this the /log switch must be passed as a param at VS Startup.
            // Error Log Paths:
            // VS 10.0     : %AppData%\Microsoft\VisualStudio\10.0\ActivityLog.XML
            // VS 10.0 Exp : %AppData%\Microsoft\VisualStudio\10.0Exp\ActivityLog.XML
            ActivityLog.log = GetService(typeof(SVsActivityLog)) as IVsActivityLog;

            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));

            base.Initialize();

            this.MenuCommandTextRunning = Resources.ExtensionName + " (Running)";
            this.MenuCommandTextStopped = Resources.ExtensionName + " (Not Running)";

            dte2 = (DTE2)GetService(typeof(DTE));

            autoShelve = new TfsAutoShelve();
            autoShelve.OnTfsAutoShelveExecutation += new TfsAutoShelve.TfsAutoShelveExecution(autoShelve_OnAutoShelveExecutation);

            // Get solution
            solution = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) as IVsSolution2;

            if (solution != null)
            {
                // Register for solution events
                solution.AdviseSolutionEvents(this, out solutionEventsCookie);
            }

            // get a reference to the options page.
            this.Options = (OptionsPageGeneral)GetDialogPage(typeof(OptionsPageGeneral));

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;

            if (null != mcs)
            {
                CommandID id = new CommandID(GuidList.guidParkware_AutoShelveCmdSet, (int)PkgCmdIDList.cmdidAutoShelveDynamicText);
                this.MenuCommandRunState = new OleMenuCommand(new EventHandler(MenuItemCallback), id) { Text = this.MenuCommandTextStopped, Enabled = false };
                mcs.AddCommand(this.MenuCommandRunState);

                CommandID menuCommandID2 = new CommandID(GuidList.guidParkware_AutoShelveCmdSet, (int)PkgCmdIDList.cmdidAutoShelveNow);
                this.MenuCommandRunNow = new OleMenuCommand(MenuItemCallback2, menuCommandID2) { Enabled = true };
                mcs.AddCommand(this.MenuCommandRunNow);
            }
        }

        /// <summary>
        /// Handles the OnAutoShelveExecutation event.
        /// </summary>
        /// <param name="e">The <see cref="CoryCissellMikeParks.Parkware_AutoShelve.TfsAutoShelveEventArgs"/> instance containing the event data.</param>
        void autoShelve_OnAutoShelveExecutation(TfsAutoShelveEventArgs e)
        {
            if (e.ExecutionSuccess)
            {
                dte2.StatusBar.Text = string.Format(Resources.ExtensionName + " shelved {0} pending changes", e.ShelvesetCount.ToString());
            }
            else
            {
                dte2.StatusBar.Text = Resources.ExtensionName + " encountered an error";

                ActivityLog.LogError(e.ExecutionException.Message, e.ExecutionException.StackTrace);
            }
        }

        void autoShelve_OnTfsAutoShelveWorkSpaceDiscovery(bool workSpaceDiscovered)
        {
            this.MenuCommandRunState.Enabled = workSpaceDiscovered;
        }

        /// <summary>
        /// Indicates to the user if the auto shelve is running or not.
        /// </summary>
        private void DisplayRunState()
        {
            dte2.StatusBar.Text = string.Format(Resources.ExtensionName + " is{0} running", autoShelve.IsRunning ? string.Empty : " not");
            ToggleMenuCommandText(this.MenuCommandRunState);
        }

        /// <summary>
        /// Menus the item callback.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void MenuItemCallback(object sender, EventArgs e)
        {
            autoShelve.ToggleTimerRunState();
        }

        private void MenuItemCallback2(object sender, EventArgs e)
        {
            TeamFoundationServerExt tfsExt = GetTFSExt();
            
            if (tfsExt != null)
                autoShelve.SaveShelveset(tfsExt.ActiveProjectContext.DomainUri);
        }

        void Options_OnIntervalChanged()
        {
            if (autoShelve != null)
            {
                autoShelve.TimerInterval = this.Options.TimerSaveInterval;
                autoShelve.BaseShelvesetName = this.Options.BaseName;
            }
        }

        /// <summary>
        /// Toggles the menu command text to indicate if the timer is running or stopped.
        /// </summary>
        /// <param name="sender">The sender.</param>
        private void ToggleMenuCommandText(object sender)
        {
            OleMenuCommand command = sender as OleMenuCommand;

            if (null == command)
                return;

            if (command.CommandID.Guid != GuidList.guidParkware_AutoShelveCmdSet)
                return;

            if (autoShelve.IsRunning)
            {
                command.Text = this.MenuCommandTextRunning;
            }
            else
            {
                command.Text = this.MenuCommandTextStopped;
            }
        }

        private TeamFoundationServerExt GetTFSExt()
        {
            // Get access to the TFS objects
            TeamFoundationServerExt tfsExt = this.dte2.GetObject(TeamFoundationServerExtService) as TeamFoundationServerExt;

            // Make sure the user is connected to a Team Foundation Server
            if (tfsExt.ActiveProjectContext.DomainUri == null || tfsExt.ActiveProjectContext.ProjectUri == null)
            {
                MessageBox.Show(Resources.ErrorNotConnected, Resources.ExtensionName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                return null;
            }
            else
            {
                return tfsExt;
            }
        }
    }
}